﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SCG.General
{
    public class RayTracer
    {
        //constructor
        public RayTracer(IRayTracerRegion region)
        {
            _region = region;
        }

        //do we have LOS to a target square?
        public bool HasLOS(int x, int y, int x2, int y2)
        {
            int diffX = x2 - x;
            int diffY = y2 - y;
            if (diffY == 0)
            {
                int inc = diffX > 0 ? 1 : -1;
                int tx = x + inc;
                while (tx != x2)
                {
                    if (_region.BlocksLOS(tx, y))
                        return false;
                    tx += inc;
                }
                return true;
            }
            else if (diffX == 0)
            {
                int inc = diffY > 0 ? 1 : -1;
                int ty = y + inc;
                while (ty != y2)
                {
                    if (_region.BlocksLOS(x, ty))
                        return false;
                    ty += inc;
                }
                return true;
            }
            else
            {
                float incX, incY;
                diffX = Math.Abs(diffX);
                diffY = Math.Abs(diffY);
                if (diffX > diffY)
                {
                    incX = 1.0f;
                    incY = Math.Abs((float)diffY / (float)diffX);
                }
                else
                {
                    incY = 1.0f;
                    incX = Math.Abs((float)diffX / (float)diffY);
                }
                if (x2 < x)
                    incX *= -1.0f;
                if (y2 < y)
                    incY *= -1.0f;
                float tx = x;
                float ty = y;
                do
                {
                    tx += incX;
                    ty += incY;
                    int itx = (int)(Math.Round(tx));
                    int ity = (int)(Math.Round(ty));
                    if (itx == x2 && ity == y2)
                        return true;
                    if (!_region.InBounds(itx, ity))
                        return true;
                    if (_region.BlocksLOS(itx, ity))
                        return false;
                }
                while (true);
            }
        }

        //return an array of points that are in LOS
        public List<SCGPoint> RayTrace(int x, int y, int range)
        {
            _range = range;
            _sourceX = x;
            _sourceY = y;
            _pts.Clear();
            for (int yt = y - range; yt <= y + range; yt++)
                for (int xt = x - range; xt <= x + range; xt++)
                    if (HasLOS(x, y, xt, yt))
                        _pts.Add(new SCGPoint(xt, yt));

            //direct paths
            LightPath(x, y, 0, -1, 1, 0);
            LightPath(x, y, 1, 0, 0, 1);
            LightPath(x, y, 0, 1, 1, 0);
            LightPath(x, y, -1, 0, 0, 1);

            return _pts;
        }

        //process direct paths
        private void LightPath(int x, int y, int addX, int addY, int offX, int offY)
        {
            while (_region.InBounds(x, y))
            {
                AddPoint(x, y);
                AddPoint(x + offX, y + offY);
                AddPoint(x - offX, y - offY);
                if (_region.BlocksLOS(x, y))
                    return;
                x += addX;
                y += addY;
            }
        }

        //add a point
        private bool AddPoint(int x, int y)
        {
            SCGPoint pt = new SCGPoint(x, y);
            if (!_pts.ContainsPoint(pt))
            {
                if (Math.Abs(x - _sourceX) <= _range && Math.Abs(y - _sourceY) <= _range)
                {
                    _pts.Add(pt);
                    return true;
                }
                else
                    return false;
            }
            return false;
        }

        //private members
        private IRayTracerRegion _region;
        private List<SCGPoint> _pts = new List<SCGPoint>();
        private int _range;
        private int _sourceX;
        private int _sourceY;
    }
}
